﻿/*
 * Jonathan
 * Created by: Jonathan Montiverdi
 * Date: 7/25/2012
 * License: http://jonathan.codeplex.com/license
 */
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Net;

using Jonathan.Data;
using Monti.MVVM;
using Monti.MVVM.Services;

namespace Jonathan.Logic
{
   /// <summary>
   /// Description of Downloader.
   /// </summary>
   public class Downloader : ViewModelBase, IDisposable
   {
      #region Fields
      
      private static bool s_HasInstance;
      private static Downloader s_Instance;
      
      private readonly WebClient m_WebClient;
      private Download m_CurrentDownload;
      
      public static event EventHandler InstanceCreated;
      
      #endregion
      
      #region Setup
      
      /// <summary>
      /// Initializes a new downloader
      /// </summary>
      private Downloader ()
      {
         this.m_WebClient = new WebClient ();
         this.m_WebClient.Proxy.Credentials = new NetworkCredential ("jonathan@seekdeepwithin.com", "jonathan");
         this.m_WebClient.Credentials = new NetworkCredential ("jonathan@seekdeepwithin.com", "jonathan");
         this.m_WebClient.DownloadProgressChanged += this.WebProgressChanged;
         this.m_WebClient.DownloadDataCompleted += this.WebCompleted;
         this.Queue = new ObservableCollection <Download> ();
      }
      
      #endregion
      
      #region Properties
      
      /// <summary>
      /// Gets if an instance was created or not.
      /// </summary>
      public static bool HasInstance { get { return s_HasInstance; } }
      
      /// <summary>
      /// Gets the downloader instance.
      /// </summary>
      public static Downloader Instance
      {
         get
         {
            if (s_Instance == null)
            {
               s_HasInstance = true;
               s_Instance = new Downloader ();
               if (InstanceCreated != null)
                  InstanceCreated (s_Instance, EventArgs.Empty);
            }
            
            return s_Instance;
         }
      }
      
      /// <summary>
      /// Gets the current download.
      /// </summary>
      public Download CurrentDownload
      {
         get { return this.m_CurrentDownload;}
         private set
         {
            this.m_CurrentDownload = value;
            this.RaisePropertyChanged ("CurrentDownload");
         }
      }
      
      /// <summary>
      /// Gets the list of downloads.
      /// </summary>
      public ObservableCollection <Download> Queue { get; private set; }
      
      #endregion
      
      #region Methods
      
      /// <summary>
      /// Adds the download to the queue.
      /// </summary>
      /// <param name="fileName">Name of item to download.</param>
      /// <param name="localPath">Local path to save item to.</param>
      public static Download AddToQueue (string fileName, string localPath)
      {
         return Instance.AddDownload (fileName, localPath);
      }
      
      /// <summary>
      /// Downloads the given file on the current thread.
      /// </summary>
      /// <param name="fileName">Name of item to download.</param>
      /// <param name="localPath">Local path to save item to.</param>
      public static void Download (string fileName, string localPath)
      {
         Instance.DownloadFile (fileName, localPath);
      }
      
      /// <summary>
      /// Downloads the given file on the current thread.
      /// </summary>
      /// <param name="fileName">Name of item to download.</param>
      /// <param name="localPath">Local path to save item to.</param>
      private void DownloadFile (string fileName, string localPath)
      {
         try
         {
            var data = this.m_WebClient.DownloadData (fileName);
            using (var file = File.Create (localPath + "_TEMP"))
            {
               file.Write (data, 0, data.Length);
               file.Flush ();
            }
            if (File.Exists (localPath))
               File.Delete (localPath);
            File.Move (localPath + "_TEMP", localPath);
         }
         catch (NotSupportedException ex)
         {
            var message = ServiceManager.Resolve <IMessageBoxService> ();
            message.ShowError (ex.Message);
         }
         catch (WebException ex)
         {
            var message = ServiceManager.Resolve <IMessageBoxService> ();
            message.ShowError (ex.Message);
         }
         finally
         {
            if (File.Exists (localPath + "_TEMP"))
               File.Delete (localPath + "_TEMP");
         }
      }
      
      /// <summary>
      /// Adds the download to the queue.
      /// </summary>
      /// <param name="fileName">Name of item to download.</param>
      /// <param name="localPath">Local path to save item to.</param>
      private Download AddDownload (string fileName, string localPath)
      {
         var download = new Download { ServerFile = fileName, LocalFile = localPath };
         this.Queue.Add (download);
         if (this.CurrentDownload == null)
            this.StartNextDownload ();
         return download;
      }
      
      /// <summary>
      /// Starts the next item to download.
      /// </summary>
      private void StartNextDownload ()
      {
         if (this.Queue.Count > 0)
         {
            var download = this.Queue [0];
            this.Queue.RemoveAt (0);
            this.CurrentDownload = download;
            try
            {
               this.m_WebClient.DownloadDataAsync (new Uri ("ftp://seekdeepwithin.com/" +
                                                            this.CurrentDownload.ServerFile));
            }
            catch (WebException ex)
            {
               var message = ServiceManager.Resolve <IMessageBoxService> ();
               message.ShowError (ex.Message);
            }
         }
      }
      
      /// <summary>
      /// Occurs when the progress of a download changes.
      /// </summary>
      /// <param name="sender">WebClient object.</param>
      /// <param name="e">DownloadProgressChangedEventArgs</param>
      private void WebProgressChanged (object sender, DownloadProgressChangedEventArgs e)
      {
         this.CurrentDownload.Progress = e.ProgressPercentage;
      }
      
      /// <summary>
      /// Occurs when the progress of a download changes.
      /// </summary>
      /// <param name="sender">WebClient object.</param>
      /// <param name="e">DownloadDataCompletedEventArgs</param>
      private void WebCompleted (object sender, DownloadDataCompletedEventArgs e)
      {
         try
         {
            using (var file = File.Create (this.m_CurrentDownload.LocalFile + "_TEMP"))
            {
               file.Write (e.Result, 0, e.Result.Length);
               file.Flush ();
            }
            if (File.Exists (this.m_CurrentDownload.LocalFile))
               File.Delete (this.m_CurrentDownload.LocalFile);
            File.Move (this.m_CurrentDownload.LocalFile + "_TEMP", this.m_CurrentDownload.LocalFile);
            this.m_CurrentDownload.WasSuccessful = true;
         }
         catch (Exception)
         {
            var message = ServiceManager.Resolve <IMessageBoxService> ();
            message.ShowError ("Error occured while downloading " + this.m_CurrentDownload.ServerFile);
         }
         finally
         {
            if (File.Exists (this.m_CurrentDownload.LocalFile + "_TEMP"))
               File.Delete (this.m_CurrentDownload.LocalFile + "_TEMP");
         }
         this.CurrentDownload.IsCompleted = true;
         this.CurrentDownload = null;
         this.StartNextDownload ();
      }
      
      /// <summary>
      /// Releases all resources.
      /// </summary>
      public void Dispose()
      {
         if (this.m_WebClient != null)
         {
            this.m_WebClient.DownloadProgressChanged -= this.WebProgressChanged;
            this.m_WebClient.DownloadDataCompleted -= this.WebCompleted;
            this.m_WebClient.Dispose ();
         }
      }
      
      #endregion
   }
}
